/*
 * @(#)ConstantsSummaryBuilder.java 1.7 03/12/19
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. SUN
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.tools.doclets.internal.toolkit.builders;

import com.sun.tools.doclets.internal.toolkit.util.*;
import com.sun.tools.doclets.internal.toolkit.*;
import com.sun.javadoc.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;

/**
 * Builds the Constants Summary Page.
 * 
 * This code is not part of an API. It is implementation that is subject to
 * change. Do not use it as an API
 * 
 * @author Jamie Ho
 * @since 1.5
 */
public class ConstantsSummaryBuilder extends AbstractBuilder {
	
	/**
	 * The root element of the constant summary XML is {@value}.
	 */
	public static final String ROOT = "ConstantSummary";
	
	/**
	 * The maximum number of package directories shown in the constant value
	 * index.
	 */
	public static final int MAX_CONSTANT_VALUE_INDEX_LENGTH = 2;
	
	/**
	 * The writer used to write the results.
	 */
	protected ConstantsSummaryWriter writer;
	
	/**
	 * The set of ClassDocs that have constant fields.
	 */
	protected Set classDocsWithConstFields;
	
	/**
	 * The set of printed package headers.
	 */
	protected Set printedPackageHeaders;
	
	/**
	 * The current package being documented.
	 */
	private PackageDoc currentPackage;
	
	/**
	 * The current class being documented.
	 */
	private ClassDoc currentClass;
	
	/**
	 * Construct a new ConstantsSummaryBuilder.
	 * 
	 * @param configuration the current configuration of the doclet.
	 */
	private ConstantsSummaryBuilder(Configuration configuration) {
		super(configuration);
	}
	
	/**
	 * Construct a ConstantsSummaryBuilder.
	 * 
	 * @param configuration the configuration used in this run of the doclet.
	 * @param writer the writer for the summary.
	 */
	public static ConstantsSummaryBuilder getInstance(
			Configuration configuration, ConstantsSummaryWriter writer) {
		ConstantsSummaryBuilder builder = new ConstantsSummaryBuilder(
				configuration);
		builder.writer = writer;
		builder.classDocsWithConstFields = new HashSet();
		return builder;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void invokeMethod(String methodName, Class[] paramClasses,
			Object[] params) throws Exception {
		if (DEBUG) {
			configuration.root.printError("DEBUG: " + this.getClass().getName()
					+ "." + methodName);
		}
		Method method = this.getClass().getMethod(methodName, paramClasses);
		method.invoke(this, params);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public void build() throws IOException {
		if (writer == null) {
			// Doclet does not support this output.
			return;
		}
		build(LayoutParser.getInstance(configuration).parseXML(ROOT));
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getName() {
		return ROOT;
	}
	
	/**
	 * Build the constant summary.
	 * 
	 * @param elements the list of elements describing constant summary
	 *        documentation.
	 */
	public void buildConstantSummary(List elements) throws Exception {
		build(elements);
		writer.close();
	}
	
	/**
	 * Build the header.
	 */
	public void buildHeader() {
		writer.writeHeader();
	}
	
	/**
	 * Build the footer.
	 */
	public void buildFooter() {
		writer.writeFooter();
	}
	
	/**
	 * Build the table of contents.
	 */
	public void buildContents() {
		writer.writeContentsHeader();
		PackageDoc[] packages = configuration.packages;
		printedPackageHeaders = new HashSet();
		for (int i = 0; i < packages.length; i++) {
			if (hasConstantField(packages[i])
					&& !hasPrintedPackageIndex(packages[i].name())) {
				writer.writeLinkToPackageContent(packages[i],
						parsePackageName(packages[i].name()),
						printedPackageHeaders);
			}
		}
		writer.writeContentsFooter();
	}
	
	/**
	 * Build the summary for each documented package.
	 * 
	 * @param elements the XML elements that represent the components of
	 *        documentation for each package.
	 */
	public void buildConstantSummaries(List elements) {
		PackageDoc[] packages = configuration.packages;
		printedPackageHeaders = new HashSet();
		for (int i = 0; i < packages.length; i++) {
			if (hasConstantField(packages[i])) {
				currentPackage = packages[i];
				// Build the documentation for the current package.
				build(elements);
			}
		}
	}
	
	/**
	 * Build the summary for the current package.
	 * 
	 * @param elements the list of XML elements that make up package
	 *        documentation.
	 */
	public void buildPackageConstantSummary(List elements) {
		build(elements);
	}
	
	/**
	 * Build the summary for the current class.
	 * 
	 * @param elements the list of XML elements that make up the class constant
	 *        summary.
	 */
	public void buildClassConstantSummary(List elements) {
		ClassDoc[] classes = currentPackage.name().length() > 0
				? currentPackage.allClasses()
				: configuration.classDocCatalog.allClasses(DocletConstants.DEFAULT_PACKAGE_NAME);
		Arrays.sort(classes);
		for (int i = 0; i < classes.length; i++) {
			if (!classDocsWithConstFields.contains(classes[i])
					|| !classes[i].isIncluded()) {
				continue;
			}
			currentClass = classes[i];
			// Build the documentation for the current class.
			build(elements);
		}
	}
	
	/**
	 * Build the header for the given class.
	 */
	public void buildPackageHeader() {
		String parsedPackageName = parsePackageName(currentPackage.name());
		if (!printedPackageHeaders.contains(parsedPackageName)) {
			writer.writePackageName(currentPackage,
					parsePackageName(currentPackage.name()));
			printedPackageHeaders.add(parsedPackageName);
		}
	}
	
	/**
	 * Build the header for the given class.
	 */
	public void buildClassHeader() {
		writer.writeConstantMembersHeader(currentClass);
	}
	
	/**
	 * Print summary of constant members in the class.
	 */
	public void buildConstantMembers() {
		new ConstantFieldBuilder(currentClass).buildMembersSummary();
	}
	
	/**
	 * Build the footer for the given class.
	 */
	public void buildClassFooter() {
		writer.writeConstantMembersFooter(currentClass);
	}
	
	/**
	 * Return true if the given package has constant fields to document.
	 * 
	 * @param pkg the package being checked.
	 * @return true if the given package has constant fields to document.
	 */
	private boolean hasConstantField(PackageDoc pkg) {
		ClassDoc[] classes;
		if (pkg.name().length() > 0) {
			classes = pkg.allClasses();
		} else {
			classes = configuration.classDocCatalog.allClasses(DocletConstants.DEFAULT_PACKAGE_NAME);
		}
		boolean found = false;
		for (int j = 0; j < classes.length; j++) {
			if (classes[j].isIncluded() && hasConstantField(classes[j])) {
				found = true;
			}
		}
		return found;
	}
	
	/**
	 * Return true if the given class has constant fields to document.
	 * 
	 * @param classDoc the class being checked.
	 * @return true if the given package has constant fields to document.
	 */
	private boolean hasConstantField(ClassDoc classDoc) {
		VisibleMemberMap visibleMemberMapFields = new VisibleMemberMap(
				classDoc, VisibleMemberMap.FIELDS, configuration.nodeprecated);
		List fields = visibleMemberMapFields.getLeafClassMembers(configuration);
		for (Iterator iter = fields.iterator(); iter.hasNext();) {
			FieldDoc field = (FieldDoc) iter.next();
			if (field.constantValueExpression() != null) {
				classDocsWithConstFields.add(classDoc);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Return true if the given package name has been printed. Also return true
	 * if the root of this package has been printed.
	 * 
	 * @param pkgname the name of the package to check.
	 */
	private boolean hasPrintedPackageIndex(String pkgname) {
		String[] list = (String[]) printedPackageHeaders.toArray(new String[] {});
		for (int i = 0; i < list.length; i++) {
			if (pkgname.startsWith(list[i])) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Print the table of constants.
	 * 
	 * @author Jamie Ho
	 * @since 1.4
	 */
	private class ConstantFieldBuilder {
		
		/**
		 * The map used to get the visible variables.
		 */
		protected VisibleMemberMap visibleMemberMapFields = null;
		
		/**
		 * The map used to get the visible variables.
		 */
		protected VisibleMemberMap visibleMemberMapEnumConst = null;
		
		/**
		 * The classdoc that we are examining constants for.
		 */
		protected ClassDoc classdoc;
		
		/**
		 * Construct a ConstantFieldSubWriter.
		 * 
		 * @param classdoc the classdoc that we are examining constants for.
		 */
		public ConstantFieldBuilder(ClassDoc classdoc) {
			this.classdoc = classdoc;
			visibleMemberMapFields = new VisibleMemberMap(classdoc,
					VisibleMemberMap.FIELDS, configuration.nodeprecated);
			visibleMemberMapEnumConst = new VisibleMemberMap(classdoc,
					VisibleMemberMap.ENUM_CONSTANTS, configuration.nodeprecated);
		}
		
		/**
		 * Builds the table of constants for a given class.
		 */
		protected void buildMembersSummary() {
			List members = new ArrayList(members());
			if (members.size() > 0) {
				Collections.sort(members);
				writer.writeConstantMembers(classdoc, members);
			}
		}
		
		/**
		 * Return the list of visible constant fields for the given classdoc.
		 * 
		 * @param cd the classdoc to examine.
		 * @return the list of visible constant fields for the given classdoc.
		 */
		protected List members() {
			List l = visibleMemberMapFields.getLeafClassMembers(configuration);
			if (l == null)
				return null;
			l.addAll(visibleMemberMapEnumConst.getLeafClassMembers(configuration));
			Iterator iter = l.iterator();
			
			List inclList = new LinkedList();
			FieldDoc member;
			while (iter.hasNext()) {
				member = (FieldDoc) iter.next();
				if (member.constantValue() != null) {
					inclList.add(member);
				}
			}
			return inclList;
		}
	}
	
	/**
	 * Parse the package name. We only want to display package name up to 2
	 * levels.
	 */
	private String parsePackageName(String pkgname) {
		int index = -1;
		for (int j = 0; j < MAX_CONSTANT_VALUE_INDEX_LENGTH; j++) {
			index = pkgname.indexOf(".", index + 1);
		}
		if (index != -1) {
			pkgname = pkgname.substring(0, index);
		}
		return pkgname;
	}
}
